package org.example.controller;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import org.example.pojo.Products;
import org.example.pojo.RedisDataExpire;
import org.example.pojo.Result;
import org.example.service.ProductsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.bind.annotation.*;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.List;
import java.util.concurrent.TimeUnit;

@RestController
@RequestMapping("shop")
public class ShopController 
{
	@Autowired
	ProductsService productsService;
	
	@Autowired
	RedisTemplate redisTemplate;
	
	@GetMapping("list")
	public List<Products> list()
	{
		return productsService.list();
	}

	/**
	 * 使用redis缓存的步骤
	 */
	
	//redis key前缀
	public static final String PRODUCT_PRE = "product:";
	
	@GetMapping("get")
	public Result get(@RequestParam Integer pid)
	{
		//redis key
		String key = PRODUCT_PRE+pid;
		//1.redis查询
		Products products = (Products)redisTemplate.opsForValue().get(key);
		
		//如果redis没查到去数据库查询
		if(products != null)
		{
			return Result.ok("查询成功",products);
		}
		
		//如果没有查到数据就就返回404
		QueryWrapper<Products> qw = new QueryWrapper<>();
		qw.eq("product_id",pid);
		Products one = productsService.getOne(qw);
		if(one == null)
		{
			return Result.error("数据不存在");
		}
		
		//查到数据就将数据保存到redis
//		redisTemplate.opsForValue().set(key,one);
		//添加过期时间，防止读到旧数据
		redisTemplate.opsForValue().set(key,one,10, TimeUnit.MINUTES);//设置10分钟过期

		return Result.ok("查询成功",one);
	}
	
	@PutMapping("{pid}/price/{price}")
	public Result updatePrice(@PathVariable Integer pid,@PathVariable BigDecimal price)
	{
		//redis key
		String key = PRODUCT_PRE+pid;
		//1.首先更新数据库的数据
		Products byId = productsService.getById(pid);
		byId.setPrice(price);
		productsService.updateById(byId);

		//2.删除redis缓存
		redisTemplate.delete(key);
		
		//3.返回结果
		return Result.ok("查询成功",byId);
	}

	/**
	 * 处理缓存穿透
	 */
	//返回空结果，设置过期时间
	@GetMapping("getNull")
	public Result getNull(@RequestParam Integer pid)
	{
		//redis key
		String key = PRODUCT_PRE+pid;
		//1.redis查询
		Products products = (Products)redisTemplate.opsForValue().get(key);

		//如果redis没查到去数据库查询
		if(products != null || redisTemplate.hasKey(key)) //数据不等于空或者key是存在的
		{
			return Result.ok("查询成功",products);
		}
		
		//查到数据就将数据保存到redis
		QueryWrapper<Products> qw = new QueryWrapper<>();
		qw.eq("product_id",pid);
		Products one = productsService.getOne(qw);
		//添加过期时间，防止读到旧数据
		redisTemplate.opsForValue().set(key,one,10, TimeUnit.MINUTES);//设置10分钟过期

		return Result.ok("查询成功",one);
	}
	
	/**
	 *	处理缓存击穿问题 
	 */
	
	//1.采用互斥锁方法，强一致性
	private static final String LOCK = "shop:lock:"; 
	@GetMapping("mutexGet")
	public Result mutexGet(@RequestParam Integer pid) 
	{
		//redis key
		String key = PRODUCT_PRE+pid;
		//1.redis查询
		Products products;
		while( (products = (Products)redisTemplate.opsForValue().get(key)) == null) //如果查不到数据就阻塞在这里
		{
			//上锁的key
			String lockKey = LOCK + pid;
			//设置过期时间，防止死锁
			//上锁，将其它线程阻塞、
			if (redisTemplate.opsForValue().setIfAbsent(lockKey, "lock", 60, TimeUnit.SECONDS)) 
			{
				try 
				{
					//查询数据库
					QueryWrapper<Products> qw = new QueryWrapper<>();
					qw.eq("product_id", pid);
					Products one = productsService.getOne(qw);
					if (one == null) 
					{
						throw new RuntimeException("数据不存在");
					}
					redisTemplate.opsForValue().set(key, one, 10, TimeUnit.MINUTES);//设置10分钟过期

				}
				finally 
				{
					redisTemplate.delete(lockKey);//解锁
				}
				
			}
		}

		return Result.ok("查询成功",products);
	}
	//2.采用逻辑过期，可用性高
	@GetMapping("expireGet")
	public Result expireGet(@RequestParam Integer pid)
	{
		//redis key
		String key = PRODUCT_PRE+pid;
		//1.redis查询
		RedisDataExpire redisDataExpire = (RedisDataExpire)redisTemplate.opsForValue().get(key);
		
		
		//如果redis没查到去数据库查询
		if(redisDataExpire == null || redisDataExpire.getExpire().isBefore(LocalDateTime.now()))
		{
			String lockKey = LOCK + pid;
			//上锁
			if(redisTemplate.opsForValue().setIfAbsent(lockKey, "lock", 60, TimeUnit.SECONDS))
			{
				//异步处理
				Thread t = new Thread(){
					@Override
					public void run() {
						try {
							//查询数据库
							QueryWrapper<Products> qw = new QueryWrapper<>();
							qw.eq("product_id",pid);
							Products one = productsService.getOne(qw);
							if(one == null)
							{
								throw new RuntimeException("数据不存在");
							}
							//创建对象，将数据库查到的对象放进去
							RedisDataExpire data = new RedisDataExpire();
							data.setO(one);
							data.setExpire(LocalDateTime.now().plusSeconds(30));//设置30秒后过期
							
							redisTemplate.opsForValue().set(key,data);//设置10分钟过期
						}
						finally {
							redisTemplate.delete(lockKey);//解锁
						}
					}
				};
				t.start();
			}
		}

		return Result.ok("查询成功",redisDataExpire==null?null:redisDataExpire.getO());
	}
}
